#include <iostream>
#include <cstdlib>
#include <string>
#include <string.h>
#include <map>
#include "node.h"
using namespace std;

DataType::DataType(string type) {
    
    if (type == "int") {
        this->type_ = 0;
    } else if (type == "float") {
        this->type_ = 1;
    }
}

float DataType::eval(map<string, FunctionInfo> &f) {
    return this->type_;
}

Number::Number(float value) {
    
	this->value_ = value;
}

float Number::eval(map<string, FunctionInfo> &f) {
	return this->value_;
}
    
Ident::Ident(string name) {
    
	this->name_ = name;
}

float Ident::eval(map<string, FunctionInfo> &f) {
     float ident_value = 0;
     map<string, VarInfo> &T = f[current_function].symbol_table;
     map<string, VarInfo> &T_global = f["1_global"].symbol_table;
     if (this->name_ != "1_dummy") {
         if (T.find(this->name_) == T.end() && T_global.find(this->name_) == T_global.end()) {
                cout << "No variable '" << this->name_ << "' found" << endl;
                exit(-1);
         }
     }
     if (T.find(this->name_) != T.end()) {
         ident_value = T[this->name_].value;
     } else {
         //cout << "In global scope for " << this->name_ << endl;
         ident_value = T_global[this->name_].value;
     }
     /*if (T[this->name_].scope > current_scope) {
         cout << "Variable '" << this->name_ << "' not in current scope" << endl;
         exit(-1);
     }*/
	return ident_value;
}

Arg::Arg(int type, Ident *argument) {
    this->type_ = type;
    this->argument_ = argument;
}

float Arg::eval(map<string, FunctionInfo> &f) {
    map<string, VarInfo> &T = f[current_function].symbol_table;
    T[argument_->name_].value = 0;
    T[argument_->name_].type = this->type_;
    return T[argument_->name_].value;
}

CallArg::CallArg(Expr *expr){
    this->expr_ = expr;
    this->arg_ = NULL;
}

CallArg::CallArg(Arg *arg, Expr *expr) {
    this->arg_ = arg;
    this->expr_ = expr;
}

float CallArg::eval(map<string, FunctionInfo> &f) {
    map<string, VarInfo> &T = f[current_function].symbol_table;
    float result = 0;
    if (current_function != "1_global") {  
        //The expression must be evaluated using the parent function's symbol table
        //in case the current function is not "1_global"
        string temp = current_function;
        current_function = f[current_function].parent_function;
        result = expr_->eval(f);
        current_function = temp;
    } else {
        //otherwise do not change the current function and directly evaluate the 
        //expression
        result = expr_->eval(f);
    }
    //get name and type of the argument
    //perform type checking
    //assign the value in the symbol table
    if (arg_ != NULL) {
        //cout << "result = " << result << endl;
        if (arg_->getArgType() == 0) {  //the argument type is an int
            if (result - (int)result != 0) {    //but the result is a float
                cout << "Cannot assign 'float' value to an 'int' argument" 
                     << "'" << arg_->getArgument()->name_ << "'" << endl;
                exit(-1);
            }
        }
        T[arg_->getArgument()->name_].value = result;
        return T[arg_->getArgument()->name_].value;
    }
}

Assign::Assign(Node *operand1, Node *operand2, bool check_exists) {
    this->operand1_ = operand1;
    this->operand2_ = operand2;
    this->check_exists_ = check_exists;
    
}

float Assign::eval(map<string, FunctionInfo> &f) {
    float result = 0;

    map<string, VarInfo> &T = f[current_function].symbol_table;
    map<string, VarInfo> &T_global = f["1_global"].symbol_table;
    Ident *variable = (Ident *)operand1_;

    float expr_value = operand2_->eval(f);

    //if the expression evaluates to a float, but the variable is of type int,
    //output an error and exit
    if (expr_value - (int)expr_value != 0 && 
        f[current_function].symbol_table[((Ident *)operand1_)->name_].type == 0) {
           cout << "Cannot assign 'float' value to 'int' variable '" << ((Ident *)operand1_)->name_ << "'" << endl;
           exit(-1);
    }
    //cout <<"Operating on variable " << variable->name_ << endl;
    if (check_exists_) {
        if (T.find(variable->name_) == T.end() && T_global.find(variable->name_) == T.end()) {
            cout << "No variable '" << variable->name_ << "' found" << endl;
            exit(-1);
        }
        if (T.find(variable->name_) == T.end()) {   //variable is in global scope
            if (((Expr *)operand2_)->getType() == "funccallstmt") {
                //cout << "here 1" << endl;
                T_global[variable->name_].value = (((FuncCall *)operand2_)->return_value_);
            } else {
                //cout << "here 2" << endl;
                T_global[variable->name_].value = operand2_->eval(f);
            }
            result = T_global[variable->name_].value;
        } else {
            if (((Expr *)operand2_)->getType() == "funccallstmt") {
                //cout << "here 3" << endl; 
                T[variable->name_].value = (((FuncCall *)operand2_)->return_value_);
            } else {
                //cout << "here 4" << endl;
                T[variable->name_].value = operand2_->eval(f);
            }
            result = T[variable->name_].value;
        }
        /*if (T[variable->name_].scope > current_scope) {
            cout << "Variable '" << variable->name_ << "' not in current scope" << endl;
            exit(-1);
        }*/
    } else {
        if (((Expr *)operand2_)->getType() == "funccallstmt") {
            T[variable->name_].value = (((FuncCall *)operand2_)->return_value_);
        } else {
            T[variable->name_].value = operand2_->eval(f);
        }
        //T[variable->name_].value = operand2_->eval(f);
    }
    /*else { //set the scope only at the time of the declaration
        T[variable->name_].scope = current_scope;
    }*/
    return result; 
}

Plus::Plus(Node *operand1, Node *operand2) {
	this->operand1_ = operand1;
	this->operand2_ = operand2;
    
}

float Plus::eval(map<string, FunctionInfo> &f) {
	return operand1_->eval(f) + operand2_->eval(f);
}

Times::Times(Node *operand1, Node *operand2) {
	this->operand1_ = operand1;
	this->operand2_ = operand2;
    
}

float Times::eval(map<string, FunctionInfo> &f) {
	return operand1_->eval(f) * operand2_->eval(f);
}

Minus::Minus(Node *operand1, Node *operand2) {
	this->operand1_ = operand1;
	this->operand2_ = operand2;
    
}

float Minus::eval(map<string, FunctionInfo> &f) {
	return operand1_->eval(f) - operand2_->eval(f);
}

Or::Or(Node *operand1, Node *operand2) {
	this->operand1_ = operand1;
	this->operand2_ = operand2;
}

float Or::eval(map<string, FunctionInfo> &f) {
	return operand1_->eval(f) || operand2_->eval(f);
}


And::And(Node *operand1, Node *operand2) {
	this->operand1_ = operand1;
	this->operand2_ = operand2;
}

float And::eval(map<string, FunctionInfo> &f) {
	return operand1_->eval(f) && operand2_->eval(f);
}


Modulus::Modulus(Node *operand1, Node *operand2) {
	this->operand1_ = operand1;
	this->operand2_ = operand2;
}

float Modulus::eval(map<string, FunctionInfo> &f) {
    float value1 = operand1_->eval(f);
    float value2 = operand2_->eval(f);
    if (value1 - (int)value1 != 0 || value2 - (int)value2 != 0) {
        cout << "Operands must evaluate to an integer to be used with the modulus operator"
             << endl;
        exit(-1);
    }
	return (int)operand1_->eval(f) % (int)operand2_->eval(f);
}


Divide::Divide(Node *operand1, Node *operand2) {
	this->operand1_ = operand1;
	this->operand2_ = operand2;
}

float Divide::eval(map<string, FunctionInfo> &f) {
	return operand1_->eval(f) / operand2_->eval(f);
}

Equals::Equals(Node *operand1, Node *operand2) {
	this->operand1_ = operand1;
	this->operand2_ = operand2;
    
}

float Equals::eval(map<string, FunctionInfo> &f) {
	return operand1_->eval(f) == operand2_->eval(f);
}

NotEquals::NotEquals(Node *operand1, Node *operand2) {
	this->operand1_ = operand1;
	this->operand2_ = operand2;
    
}

float NotEquals::eval(map<string, FunctionInfo> &f) {
	return operand1_->eval(f) == operand2_->eval(f);
}

Greater::Greater(Node *operand1, Node *operand2) {
	this->operand1_ = operand1;
	this->operand2_ = operand2;
    
}

float Greater::eval(map<string, FunctionInfo> &f) {
	return operand1_->eval(f) > operand2_->eval(f);
}

GreaterEquals::GreaterEquals(Node *operand1, Node *operand2) {
	this->operand1_ = operand1;
	this->operand2_ = operand2;
    
}

float GreaterEquals::eval(map<string, FunctionInfo> &f) {
	return operand1_->eval(f) >= operand2_->eval(f);
}

Less::Less(Node *operand1, Node *operand2) {
	this->operand1_ = operand1;
	this->operand2_ = operand2;
    
}

float Less::eval(map<string, FunctionInfo> &f) {
	return operand1_->eval(f) < operand2_->eval(f);
}

LessEquals::LessEquals(Node *operand1, Node *operand2) {
	this->operand1_ = operand1;
	this->operand2_ = operand2;
    
}

float LessEquals::eval(map<string, FunctionInfo> &f) {
	return operand1_->eval(f) <= operand2_->eval(f);
}

PlusEquals::PlusEquals(Node *operand1, Node *operand2) {
	this->operand1_ = operand1;
	this->operand2_ = operand2;
    
}

float PlusEquals::eval(map<string, FunctionInfo> &f) {
    map<string, VarInfo> &T = f[current_function].symbol_table;
    map<string, VarInfo> &T_global = f["1_global"].symbol_table;
	float answer = operand1_->eval(f) + operand2_->eval(f);
	Ident *variable = (Ident *)operand1_;
     if (T.find(variable->name_) == T.end() && T_global.find(variable->name_) == T_global.end()) {
            cout << "No variable '" << variable->name_ << "' found" << endl;
            exit(-1);
     }
     if (T.find(variable->name_) != T.end()) {
         if (answer - (int)answer != 0 &&
             T[variable->name_].type == 0) {
                cout << "Cannot assign 'float' value to 'int' variable '" << ((Ident *)operand1_)->name_ << "'" << endl;
                exit(-1);
         }
         T[variable->name_].value = answer;
     } else {
         if (answer - (int)answer != 0 &&
             T_global[variable->name_].type == 0) {
                cout << "Cannot assign 'float' value to 'int' variable '" << ((Ident *)operand1_)->name_ << "'" << endl;
                exit(-1);
         }
         T_global[variable->name_].value = answer;
     }
	 return answer;
}

MinusEquals::MinusEquals(Node *operand1, Node *operand2) {
	this->operand1_ = operand1;
	this->operand2_ = operand2;
    
}

float MinusEquals::eval(map<string, FunctionInfo> &f) {
    map<string, VarInfo> &T = f[current_function].symbol_table;
    map<string, VarInfo> &T_global = f["1_global"].symbol_table;
	float answer = operand1_->eval(f) - operand2_->eval(f);
	Ident *variable = (Ident *)operand1_;

     if (T.find(variable->name_) == T.end() && T_global.find(variable->name_) == T_global.end()) {
            cout << "No variable '" << variable->name_ << "' found" << endl;
            exit(-1);
     }
     if (T.find(variable->name_) != T.end()) {
         if (answer - (int)answer != 0 &&
             T[variable->name_].type == 0) {
             cout << "Cannot assign 'float' value to 'int' variable '" << ((Ident *)operand1_)->name_ << "'" << endl;
             exit(-1);
         }
         T[variable->name_].value = answer;
     } else {
         if (answer - (int)answer != 0 &&
             T_global[variable->name_].type == 0) {
             cout << "Cannot assign 'float' value to 'int' variable '" << ((Ident *)operand1_)->name_ << "'" << endl;
             exit(-1);
         }
         T_global[variable->name_].value = answer;
     }
	 return answer;
}


Stmt::Stmt() {
	static NullStmt nullstmt;
	next = &nullstmt;
}

Stmt *Stmt::append(Stmt *a, Stmt *b) {
	if (a == NULL || a->isNull()) {
		return b;
	}
	Stmt* current = a;
	while (! current->next->isNull()) {
		current = current->next;
	}
	current->next = b;
	return a;
}

const char* ReturnStmt::getType() { return "return";}

int ReturnStmt::numParts() { return 1;}

Node *ReturnStmt::getPart(int i) {
    if (i == 1) {
        return expr_;
    } 
} 

float ReturnStmt::eval(map<string, FunctionInfo> &f) {
    float result = expr_->eval(f);
    //check if the expression evaluates to a float in case the return type is an integer
    if (f[current_function].body->return_type_->getDataType() == 0) {
       if (result - (int)result != 0) {  //the result is a float
           cout << "Return type 'int'. Cannot return a 'float' value" << endl;
           exit(-1);
       }
    }
    //cout << "parent function = " << f[current_function].parent_function << endl;
    current_function = f[current_function].parent_function;
    return result;
}

void ReturnStmt::evalAll(map<string, FunctionInfo> &f) {
    string temp = current_function;
    float result = eval(f);
    //cout << "current function = " << temp << endl;
    f[temp].body->has_returned_ = true;
    f[temp].body->return_value_ = result;
    //cout << "result = " << result << endl;
    //cout << "The buck stops here" << endl;
}

ReturnStmt::ReturnStmt(Expr *expr) : expr_(expr) {
}

const char* FuncDeclareStmt::getType() { return "function";}

int FuncDeclareStmt::numParts() { return 6;}

Node *FuncDeclareStmt::getPart(int i) {
    if (i == 1) {
        return return_type_;
    } else if (i == 2) {
        return stmt_before_return_;
    } else if (i == 3) {
        return return_stmt_;
    } else if (i == 4) {
        return func_name_;
    } else if (i == 5) {
        return arguments_;
    } else {
        return stmt_after_return_;
    }
}
float FuncDeclareStmt::eval(map<string, FunctionInfo> &f) {
    //cout << current_function << endl;
    //cout << "Evaluating declare stmt" << endl;
    //if (this->return_expr_ == NULL) {
    //    cout << "This is really null" << endl;
    //}
    map<string, VarInfo> &T = f[this->func_name_->name_].symbol_table;
    if (arguments_->getHeadArg() != (void *)0x1) {
        if (arguments_->getHeadArg() != NULL) {
            Stmt *head_arg = arguments_->getHeadArg();
            while (strcmp(head_arg->getType(), "null") != 0) {
                Arg *temp = (Arg *)head_arg;
                T[temp->getArgument()->name_].type = temp->getArgType();
                T[temp->getArgument()->name_].value = 0;
                //cout << temp->getArgument()->name_ << " " << temp->getArgType() << endl;
                head_arg = temp->getNext();
            }
        }
    }
    //cout << "current function in FuncDeclareStmt::eval = " << this->func_name_->name_ << endl;
    f[this->func_name_->name_].body = this;
}

FuncDeclareStmt::FuncDeclareStmt(DataType *rtype, Ident *name, ArgStmt *args, 
                                 Stmt *stmt1, ReturnStmt *rstmt, Stmt *stmt2) : 
                             return_type_(rtype), func_name_(name), 
                             arguments_(args), stmt_before_return_(stmt1),
                             return_stmt_(rstmt), stmt_after_return_(stmt2) {
}


FuncCall::FuncCall(string function_name, CallArgStmt *callargs) {
    this->function_name_ = function_name;
    this->callargs_ = callargs;
}

const char *FuncCall::getType() {
    return "funccallstmt";
}

int FuncCall::numParts() {
    return 1;
}

Node *FuncCall::getPart(int i) {
    if (i == 0) {
        return callargs_;
    }
}

float FuncCall::eval(map<string, FunctionInfo> &f) {
    //A variable of the same name exists in the current of global scope
    if (f["1_global"].symbol_table.find(this->function_name_) != f["1_global"].symbol_table.end() || 
        f[current_function].symbol_table.find(this->function_name_) != f[current_function].symbol_table.end()) {
            cout << "Cannot call a function with the same name as a variable in the current or global scope" << endl;
            exit(-1);
    }
    //cout << "evaluating " << this->function_name_ << endl;
    if (f.find(this->function_name_) == f.end()) {
        cout << "No function '" << this->function_name_ << "' found" << endl;
        exit(-1);
    }
    f[this->function_name_].parent_function = current_function;
    current_function = this->function_name_;
    if (callargs_ == NULL) {
        cout << "call args is null" << endl;
    }
    //cout << "current function in funccall::eval = " << current_function << endl;
    callargs_->eval(f);
    //cout << "after eval" << endl;
    
    if (f[current_function].body->stmt_before_return_ != NULL) {
        //cout << "evaluating statement before return" << endl;
        f[current_function].body->stmt_before_return_->evalAll(f);
    }
    //cout << "reaching here : " << this->function_name_ << endl;
    if(f[this->function_name_].body->has_returned_) {
        f[this->function_name_].body->has_returned_ = false;
        this->return_value_ = f[this->function_name_].body->return_value_;
        return this->return_value_;
        //return f[this->function_name_].body->return_value_;
    } else {
        if (f[current_function].body->return_stmt_ == NULL) {
            /*f[current_function].body->stmt_after_return_->evalAll(f);
            if (f[current_function].body->has_returned_) {
                f[this->function_name_].body->has_returned_ = false;
                return f[this->function_name_].body->return_value_;
            }*/
            cout << "no return statement encountered in execution path for function '"
                 << this->function_name_ << "'" << endl;
            exit(-1);
        } 
    }
    this->return_value_ = f[current_function].body->return_stmt_->eval(f);
    //cout << "reached here too " << endl;
    return this->return_value_; 
}

const char *CallArgStmt::getType() { return "callargslist";}

int CallArgStmt::numParts() { return 1;}

Node *CallArgStmt::getPart(int i) {
    return NULL;
}

float CallArgStmt::eval(map<string, FunctionInfo> &f) {
   //cout << "starting eval" << endl;
   if (f[current_function].body == NULL) {
       cout << "body is null" << endl;
   }
   ArgStmt *arguments = f[current_function].body->arguments_;
   if (arguments == NULL) {
       cout << "arguments is null" << endl;
       exit(-1);
   }
   //count the arguments to make sure that they are equal in number
   int declare_args = 0, call_args = 0;
   if (arguments->getHeadArg() != NULL) {
       Stmt *head_arg = arguments->getHeadArg();
       //cout << ((Arg *)head_arg)->getArgument()->name_ << endl;
       if (((Arg *)head_arg)->getArgument()->name_ == "1_dummy") {
            declare_args = 0;
       } else {
           while (strcmp(head_arg->getType(), "null") != 0) {
               declare_args++;
               head_arg = head_arg->getNext();
           }
       }
   }    
   Stmt *call_head_arg = head_arg_;
   if (call_head_arg != NULL) {
       Expr *call_arg_expr = ((CallArg *)call_head_arg)->getExpr();
       if (call_arg_expr->getType() == "Ident") {
           Ident *call_arg_ident = (Ident *)call_arg_expr;
           //cout << call_arg_ident->name_ << endl;
           if (call_arg_ident->name_ == "1_dummy") {
               call_args = 0;
           }
       } else {
           while (strcmp(call_head_arg->getType(), "null") != 0) {
               call_args++;
               call_head_arg = call_head_arg->getNext();
           }
       }
   } 
   //cout << declare_args << " " << call_args << endl;
   if (declare_args != call_args) {
       cout << "Invalid number of arguments" << endl;
       exit(-1);
   }
   //Proceed if they are equal in number
   if (arguments->getHeadArg() != NULL) {
       Stmt *head_arg = arguments->getHeadArg();
       Stmt *call_head_arg = head_arg_;
       int count = 0;
       while (strcmp(head_arg->getType(), "null") != 0) {
           count++;
           ((CallArg *)call_head_arg)->setArg((Arg *)head_arg);
           call_head_arg->eval(f);
           call_head_arg = call_head_arg->getNext();
           head_arg = head_arg->getNext();
       }
   }
   return 1;
}

CallArgStmt::CallArgStmt(CallArg *head_arg) : head_arg_(head_arg) {
}

CallArg *CallArgStmt::getHeadArg() {
    return head_arg_;
}


const char* IfStmt::getType() { 
    return "if"; 
}

int IfStmt::numParts() { 
    return 3;
}

Node *IfStmt::getPart(int i) {
	if (i == 1) {
		return clause_;
	} else if ( i == 2) {
		return ifblock_;
	} else {
        return elseblock_;
    }
}

float IfStmt::eval(map<string, FunctionInfo> &f) {
    float condition = clause_->eval(f);
    if (condition > 0) {
        if (ifblock_ != NULL) {
            ifblock_->evalAll(f);
        }
    } else {
        if (elseblock_ != NULL) {
            elseblock_->evalAll(f);
        }
    }
}

const char* WhileStmt::getType() { 
    return "while"; 
}

int WhileStmt::numParts() { 
    return 2;
}

Node *WhileStmt::getPart(int i) {
	if (i == 1) {
		return clause_;
	} else {
		return block_;
	}
}

float WhileStmt::eval(map<string, FunctionInfo> &f) {
    while (clause_->eval(f) > 0) {
        if (block_ != NULL) {
            block_->evalAll(f);
        }
    } 
}


const char* ForStmt::getType() { 
    return "while"; 
}

int ForStmt::numParts() { 
    return 4;
}

Node *ForStmt::getPart(int i) {
    if (i == 1) {
		return init_;
	} else if (i == 2) {
		return condition_;
	} else if (i == 3) {
        return expr_;
    } else {
        return block_;
    }
}

float ForStmt::eval(map<string, FunctionInfo> &f) {
    for (init_->eval(f); condition_->eval(f); expr_->eval(f)) { 
        if (block_ != NULL) {
            block_->evalAll(f);
        }
    } 
}  

